home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue60 / Alfresco / AAExpr.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2000-06-25  |  15.8 KB  |  512 lines

  1. {*********************************************************}
  2. {* AAExpr                                                *}
  3. {* Copyright (c) Julian M Bucknall 1998-1999             *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Expression parser and evaluator                       *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit AAExpr;
  14.  
  15. {Version 1: initial release}
  16. {Version 2: Fixed equal precedence parsing, and overread bug}
  17.  
  18. interface
  19.  
  20. uses
  21.   SysUtils,
  22.   AAChStk,
  23.   AAStStk,
  24.   AAVarLst;
  25.  
  26. type
  27.   TaaExprTokenType = ( {Expression token types}
  28.          ttOperator,   {..an operator}
  29.          ttNumOperand, {..a numeric operand}
  30.          ttVarOperand, {..an operand that's a variable}
  31.          ttEndOfExpr); {..the end of the expression}
  32.  
  33. type
  34.   TaaExprParserState = ( {Possible parser states}
  35.      psCannotBeOperand,  {..the next token cannot be an operand}
  36.      psCouldBeOperand,   {..the next token could be an operand}
  37.      psMustBeOperand);   {..the next token must an operand or '('}
  38.  
  39. type
  40.   TaaExpressionParser = class
  41.     private
  42.       FExpr     : PChar;
  43.       FOrigExpr : PChar;
  44.       FParsed   : boolean;
  45.       FStStack  : TaaStringStack;
  46.       FOpStack  : TaaCharacterStack;
  47.       FVarList  : TaaVariableList;
  48.     protected
  49.       function epGetExpression : string;
  50.       function epGetRPNExpression : string;
  51.       function epGetValue : double;
  52.       function epGetVariable(const aName : string) : double;
  53.       procedure epSetExpression(aExpr : string);
  54.       procedure epSetVariable(const aName : string; aValue : double);
  55.  
  56.       procedure epRaiseBadExpressionError(aPosn : PChar);
  57.  
  58.       procedure epCheckBadParserState(aState    : TaaExprParserState;
  59.                                       aBadState : TaaExprParserState;
  60.                                       aCharPos  : PChar);
  61.       procedure epFindEndOfNumber;
  62.       procedure epFindEndOfIdentifier;
  63.       procedure epFormRPNSubExpr(aOp : char; aCharPos : PChar);
  64.       function epGetNextToken(var aStartToken : PChar) : TaaExprTokenType;
  65.       function epGetPrecedence(aOp : char) : integer;
  66.       procedure epParseToRPN;
  67.       procedure epPushNewOperand(aStartPos : PChar);
  68.       procedure epSkipBlanks;
  69.  
  70.     public
  71.       constructor Create(const aExpr : string);
  72.       destructor Destroy; override;
  73.  
  74.       {$IFOPT D+}
  75.       procedure TokenPrint;
  76.       {$ENDIF}
  77.  
  78.       property Expression : string
  79.          read epGetExpression write epSetExpression;
  80.       property RPNExpression : string
  81.          read epGetRPNExpression;
  82.       property Value : double
  83.          read epGetValue;
  84.       property Variable[const aName : string] : double
  85.          read epGetVariable write epSetVariable;
  86.   end;
  87.  
  88. implementation
  89.  
  90. uses
  91.   AAFltStk;
  92.  
  93. const
  94.   OperatorSet = ['(', ')', '^', '*', '/', '+', '-'];
  95.   NumberSet = ['0'..'9', '.'];
  96.   IdentifierSet = ['A'..'Z', 'a'..'z', '0'..'9', '_'];
  97.  
  98. const
  99.   UnaryMinus = char(ord('-') or $80);
  100.  
  101. {===Helper functions=================================================}
  102. function Power(X, Y : double) : double;
  103. begin
  104.   if (Y = 0.0) then
  105.     Result := 1.0
  106.   else if (Y = 1.0) then
  107.     Result := X
  108.   else
  109.     Result := exp(ln(X) * Y);
  110. end;
  111. {====================================================================}
  112.  
  113.  
  114. {===TaaExpressionParser==============================================}
  115. constructor TaaExpressionParser.Create(const aExpr : string);
  116. begin
  117.   inherited Create;
  118.   {create a string stack for the operands and an operator stack}
  119.   FStStack := TaaStringStack.Create(4096);
  120.   FOpStack := TaaCharacterStack.Create;
  121.   {create a variable list}
  122.   FVarList := TaaVariableList.Create;
  123.   {set the expression string}
  124.   Expression := aExpr;
  125. end;
  126. {--------}
  127. destructor TaaExpressionParser.Destroy;
  128. begin
  129.   Expression := '';
  130.   FStStack.Free;
  131.   FOpStack.Free;
  132.   FVarList.Free;
  133.   inherited Destroy;
  134. end;
  135. {--------}
  136. procedure TaaExpressionParser.epCheckBadParserState(
  137.                                 aState    : TaaExprParserState;
  138.                                 aBadState : TaaExprParserState;
  139.                                 aCharPos  : PChar);
  140. begin
  141.   if (aState = aBadState) then
  142.     epRaiseBadExpressionError(aCharPos);
  143. end;
  144. {--------}
  145. procedure TaaExpressionParser.epFindEndOfNumber;
  146. var
  147.   TempExpr : PChar;
  148. begin
  149.   {assume that FExpr is a digit, find the end of the stream of digits}
  150.   TempExpr := FExpr;
  151.   while (TempExpr^ in NumberSet) do
  152.     inc(TempExpr);
  153.   FExpr := TempExpr;
  154. end;
  155. {--------}
  156. procedure TaaExpressionParser.epFindEndOfIdentifier;
  157. var
  158.   TempExpr : PChar;
  159. begin
  160.   {assume that FExpr is an alphanum char, find the end of the stream
  161.    of alphanum chars}
  162.   TempExpr := FExpr;
  163.   while (TempExpr^ in IdentifierSet) do
  164.     inc(TempExpr);
  165.   FExpr := TempExpr;
  166. end;
  167. {--------}
  168. procedure TaaExpressionParser.epFormRPNSubExpr(aOp      : char;
  169.                                                aCharPos : PChar);
  170. var
  171.   PrecOp   : integer;
  172.   PrecTop  : integer;
  173.   TempOp   : char;
  174.   Operand1 : string[255];
  175.   Operand2 : string[255];
  176. begin
  177.   {this routine is called when the operator about to be pushed, aOp,
  178.    has a precedence lower than or equal to the operator on top of the
  179.    operator stack. We need to pop off some operators and operands and
  180.    form some RPN expressions to push onto the operand stack, until the
  181.    operator stack is exhausted or the top operator has a precedence
  182.    value less than the given operator's precedence value.}
  183.   PrecOp := epGetPrecedence(aOp);
  184.   PrecTop := epGetPrecedence(FOpStack.Examine);
  185.   while (PrecOp <= PrecTop) and (PrecTop > 1) do begin
  186.     TempOp := FOpStack.Pop;
  187.     if (TempOp = UnaryMinus) then begin
  188.       if (FStStack.Count = 0) then
  189.         epRaiseBadExpressionError(aCharPos);
  190.       Operand1 := FStStack.Pop + UnaryMinus;
  191.       FStStack.Push(Operand1);
  192.     end
  193.     else begin
  194.       if (FStStack.Count < 2) then
  195.         epRaiseBadExpressionError(aCharPos);
  196.       Operand2 := FStStack.Pop;
  197.       Operand1 := FStStack.Pop + Operand2 + TempOp;
  198.       FStStack.Push(Operand1);
  199.     end;
  200.     if FOpStack.IsEmpty then
  201.       PrecOp := 0
  202.     else
  203.       PrecTop := epGetPrecedence(FOpStack.Examine);
  204.   end;
  205.   {if the given operator was a right parenthesis the top of the
  206.    operator stack *must* be a left parenthesis and we should remove
  207.    it}
  208.   if (aOp = ')') then begin
  209.     if FOpStack.IsEmpty or (FOpStack.Examine <> '(') then
  210.       epRaiseBadExpressionError(aCharPos);
  211.     FOpStack.Pop;
  212.   end;
  213. end;
  214. {--------}
  215. function TaaExpressionParser.epGetExpression : string;
  216. begin
  217.   Result := StrPas(FOrigExpr);
  218. end;
  219. {--------}
  220. function TaaExpressionParser.epGetNextToken(var aStartToken : PChar)
  221.                                                    : TaaExprTokenType;
  222. var
  223.   CurChar : char;
  224. begin
  225.   epSkipBlanks;
  226.   aStartToken := FExpr;
  227.   CurChar := aStartToken^;
  228.   if (CurChar = #0) then
  229.     Result := ttEndOfExpr
  230.   else if (CurChar in OperatorSet) then begin
  231.     inc(FExpr); {operators are always one character in size}
  232.     Result := ttOperator;
  233.   end
  234.   else if (CurChar in NumberSet) then begin
  235.     epFindEndOfNumber;
  236.     Result := ttNumOperand;
  237.   end
  238.   else if (CurChar in IdentifierSet) then begin
  239.     epFindEndOfIdentifier;
  240.     Result := ttVarOperand;
  241.   end
  242.   else begin
  243.     Result := ttEndOfExpr;
  244.     epRaiseBadExpressionError(aStartToken);
  245.   end;
  246. end;
  247. {--------}
  248. function TaaExpressionParser.epGetPrecedence(aOp : char) : integer;
  249. const
  250.   Operators : string[8] = '()^*/+-' + UnaryMinus;
  251.   Precedences : array [1..8] of byte = (1,1,7,5,5,3,3,9);
  252. var
  253.   Posn : integer;
  254. begin
  255.   Posn := Pos(aOp, Operators);
  256.   Result := Precedences[Posn];
  257. end;
  258. {--------}
  259. function TaaExpressionParser.epGetRPNExpression : string;
  260. begin
  261.   if not FParsed then
  262.     epParseToRPN;
  263.   Result := FStStack.Examine;
  264. end;
  265. {--------}
  266. function TaaExpressionParser.epGetValue : double;
  267. var
  268.   DblStack : TaaFloatStack;
  269.   i        : integer;
  270.   Operand1 : double;
  271.   Operand2 : double;
  272.   Expr     : string[255];
  273.   OperandSt: string[255];
  274. begin
  275.   if not FParsed then
  276.     epParseToRPN;
  277.   {prepare a stack for doubles}
  278.   DblStack := TaaFloatStack.Create;
  279.   try
  280.     {read through the RPN expression and evaluate it}
  281.     Expr := FStStack.Examine;
  282.     i := 0;
  283.     while (i < length(Expr)) do begin
  284.       inc(i);
  285.       if (Expr[i] = ' ') then begin
  286.         if (Expr[i+1] in NumberSet) then begin
  287.           OperandSt := '';
  288.           while (i < length(Expr)) and
  289.                 (Expr[i+1] in NumberSet) do begin
  290.             OperandSt := OperandSt + Expr[i+1];
  291.             inc(i);
  292.           end;
  293.           DblStack.Push(StrToFloat(OperandSt));
  294.         end
  295.         else begin
  296.           OperandSt := '';
  297.           while (i < length(Expr)) and
  298.                 (Expr[i+1] in IdentifierSet) do begin
  299.             OperandSt := OperandSt + Expr[i+1];
  300.             inc(i);
  301.           end;
  302.           DblStack.Push(FVarList.Value[OperandSt]);
  303.         end
  304.       end
  305.       else begin
  306.         if Expr[i] = UnaryMinus then
  307.           DblStack.Push(-DblStack.Pop)
  308.         else begin
  309.           Operand2 := DblStack.Pop;
  310.           Operand1 := DblStack.Pop;
  311.           case Expr[i] of
  312.             '+' : DblStack.Push(Operand1 + Operand2);
  313.             '-' : DblStack.Push(Operand1 - Operand2);
  314.             '*' : DblStack.Push(Operand1 * Operand2);
  315.             '/' : DblStack.Push(Operand1 / Operand2);
  316.             '^' : DblStack.Push(Power(Operand1, Operand2));
  317.           end;{case}
  318.         end;
  319.       end;
  320.     end;
  321.     Result := DblStack.Pop;
  322.   finally
  323.     DblStack.Free;
  324.   end;
  325. end;
  326. {--------}
  327. function TaaExpressionParser.epGetVariable(const aName : string) : double;
  328. begin
  329.   Result := FVarList.Value[aName];
  330. end;
  331. {--------}
  332. procedure TaaExpressionParser.epParseToRPN;
  333. var
  334.   ParserState : TaaExprParserState;
  335.   TokenType   : TaaExprTokenType;
  336.   Op          : char;
  337.   StartPos    : PChar;
  338.   PrecOp      : integer;
  339.   PrecTop     : integer;
  340. begin
  341.   {if we've done this already, get out}
  342.   if FParsed then
  343.     Exit;
  344.   {initialise the operator stack to have a left parenthesis; when we
  345.    reach the end of the expression we'll be pretending it has a right
  346.    parenthesis}
  347.   FOpStack.Clear;
  348.   FOpStack.Push('(');
  349.   {initialise the operand stack}
  350.   FStStack.Clear;
  351.   {initialise the parser}
  352.   FExpr := FOrigExpr;
  353.   ParserState := psCouldBeOperand;
  354.   {get the next token from the expression}
  355.   TokenType := epGetNextToken(StartPos);
  356.   {process all the tokens}
  357.   while (TokenType <> ttEndOfExpr) do begin
  358.     {what type of token are we trying to parse?}
  359.     case TokenType of
  360.       ttOperator :
  361.         begin
  362.           {it's an operator}
  363.           Op := StartPos^;
  364.           {if the operator is a left parenthesis, just push it onto
  365.            the operator stack}
  366.           if (Op = '(') then begin
  367.             FOpStack.Push(Op);
  368.             ParserState := psCouldBeOperand;
  369.           end
  370.           else begin
  371.             epCheckBadParserState(ParserState, psMustBeOperand, StartPos);
  372.             {if the operator is a right parenthesis, start popping off
  373.              operators and operands and forming RPN subexpressions,
  374.              until we reach a left parenthesis}
  375.             if (Op = ')') then begin
  376.               if FOpStack.IsEmpty then
  377.                 epRaiseBadExpressionError(StartPos);
  378.               epFormRPNSubExpr(')', StartPos);
  379.               ParserState := psCannotBeOperand;
  380.             end
  381.             {if the operator is a unary operator, then ignore a unary
  382.              plus (it has no effect) and push a unary minus}
  383.             else if (ParserState = psCouldBeOperand) then begin
  384.               if (Op <> '+') and (Op <> '-') then
  385.                 epRaiseBadExpressionError(StartPos);
  386.               if (Op = '-') then
  387.                 FOpStack.Push(UnaryMinus);
  388.               ParserState := psMustBeOperand;
  389.             end
  390.             {if we reach this point, the operator must be pushed onto
  391.              the stack, however, we first need to check that we are
  392.              not pushing it onto an operator of greater or equal
  393.              precedence}
  394.             else begin
  395.               PrecOp := epGetPrecedence(Op);
  396.               if FOpStack.IsEmpty then
  397.                 PrecTop := 0
  398.               else
  399.                 PrecTop := epGetPrecedence(FOpStack.Examine);
  400.               if (PrecOp <= PrecTop) then
  401.                 epFormRPNSubExpr(Op, StartPos);
  402.               FOpStack.Push(Op);
  403.               ParserState := psCouldBeOperand;
  404.             end;
  405.           end;
  406.         end;
  407.       ttNumOperand,
  408.       ttVarOperand :
  409.         begin
  410.           {it's an operand}
  411.           epCheckBadParserState(ParserState, psCannotBeOperand, StartPos);
  412.           epPushNewOperand(StartPos);
  413.           ParserState := psCannotBeOperand;
  414.         end;
  415.     end;
  416.     {get the next token from the expression}
  417.     TokenType := epGetNextToken(StartPos);
  418.   end;
  419.   {at the end we pretend that the expression was terminated with a
  420.    right parenthesis and we can't be expecting an operand}
  421.   epCheckBadParserState(ParserState, psMustBeOperand, StartPos);
  422.   epFormRPNSubExpr(')', StartPos);
  423.   {at this point, the operator stack should be empty and the operand
  424.    stack should have one item: the RPN of the original expression}
  425.   if (not FOpStack.IsEmpty) or (FStStack.Count <> 1) then
  426.     epRaiseBadExpressionError(StartPos);
  427.   FParsed := true;
  428. end;
  429. {--------}
  430. procedure TaaExpressionParser.epPushNewOperand(aStartPos : PChar);
  431. var
  432.   TempStr : string[255];
  433. begin
  434.   TempStr[0] := char(succ(FExpr - aStartPos));
  435.   TempStr[1] := ' ';
  436.   Move(aStartPos^, TempStr[2], FExpr - aStartPos);
  437.   FStStack.Push(TempStr);
  438. end;
  439. {--------}
  440. procedure TaaExpressionParser.epRaiseBadExpressionError(aPosn : PChar);
  441. begin
  442.   if (aPosn = StrEnd(FOrigExpr)) then
  443.     raise Exception.Create(
  444.        'Badly formed expression detected at end of string')
  445.   else
  446.     raise Exception.Create(
  447.        Format('Badly formed expression with character [%s], at position %d',
  448.               [aPosn^, succ(aPosn - FOrigExpr)]));
  449. end;
  450. {--------}
  451. procedure TaaExpressionParser.epSetExpression(aExpr : string);
  452. begin
  453.   {first destroy the original expression}
  454.   if (FOrigExpr <> nil) then
  455.     StrDispose(FOrigExpr);
  456.   {now allocate the new one}
  457.   if (aExpr = '') then
  458.     FOrigExpr := nil
  459.   else begin
  460.     if (length(aExpr) > 255) then
  461.       raise Exception.Create('TaaExpressionParser: the expression is too long');
  462.     FOrigExpr := StrAlloc(succ(length(aExpr)));
  463.     StrPCopy(FOrigExpr, aExpr);
  464.   end;
  465.   {the expression is not yet parsed}
  466.   FParsed := aExpr = '';
  467. end;
  468. {--------}
  469. procedure TaaExpressionParser.epSetVariable(const aName : string; aValue : double);
  470. begin
  471.   FVarList.Value[aName] := aValue
  472. end;
  473. {--------}
  474. procedure TaaExpressionParser.epSkipBlanks;
  475. var
  476.   TempExpr : PChar;
  477. begin
  478.   {jump past all the blanks}
  479.   TempExpr := FExpr;
  480.   while (TempExpr^ = ' ') do
  481.     inc(TempExpr);
  482.   FExpr := TempExpr;
  483. end;
  484. {--------}
  485. {$IFOPT D+}
  486. procedure TaaExpressionParser.TokenPrint;
  487. var
  488.   i         : integer;
  489.   StartPos  : PChar;
  490.   TokenType : TaaExprTokenType;
  491. begin
  492.   FExpr := FOrigExpr;
  493.   TokenType := epGetNextToken(StartPos);
  494.   while TokenType <> ttEndOfExpr do begin
  495.     case TokenType of
  496.       ttOperator   : write('  operator:         ');
  497.       ttNumOperand : write('  number operand:   ');
  498.       ttVarOperand : write('  variable operand: ');
  499.     end;{case}
  500.     for i := 0 to pred(FExpr-StartPos) do
  501.       write((StartPos + i)^);
  502.     writeln;
  503.     TokenType := epGetNextToken(StartPos);
  504.   end;
  505.   writeln('  end of expression');
  506. end;
  507. {$ENDIF}
  508. {====================================================================}
  509.  
  510. end.
  511.  
  512.